{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Configuration\n",
    "\n",
    "Define settings for remote services, create `appsettings.Development.json` using `appsettings.json` as template.\n",
    "\n",
    "You need to manually give yourself\n",
    "- `Cognitive Services OpenAI Contributor` rights on the `Azure OpenAI` resource\n",
    "- `Search Index Data Contributor` right on the `Azure AI Search` resource\n",
    "\n",
    "if you not already have it.\n",
    "\n",
    "Also ensure to execute `azd auth login` to have access to the Azure resources.\n",
    "\n",
    "> This example is based on [Creative Writing Assistant: Working with Agents using Prompty (Python Implementation)](https://github.com/Azure-Samples/contoso-creative-writer/tree/main)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Install and Import Required Packages\n",
    "Install and import the necessary packages using NuGet."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "// Install the necessary packages using NuGet\n",
    "#r \"nuget: Azure.Identity, 1.13.1\"\n",
    "#r \"nuget: CsvHelper, 33.0.1\"\n",
    "#r \"nuget: Microsoft.Extensions.Configuration, 9.0.0\"\n",
    "#r \"nuget: Microsoft.Extensions.Configuration.Binder, 9.0.0\"\n",
    "#r \"nuget: Microsoft.Extensions.Configuration.UserSecrets, 9.0.0\"\n",
    "#r \"nuget: Microsoft.Extensions.Configuration.EnvironmentVariables, 9.0.0\"\n",
    "#r \"nuget: Microsoft.Extensions.Http.Resilience, 9.0.0\"\n",
    "#r \"nuget: Microsoft.SemanticKernel, 1.33.0\"\n",
    "#r \"nuget: Microsoft.SemanticKernel.Plugins.Core, 1.33.0-alpha\"\n",
    "#r \"nuget: Microsoft.SemanticKernel.Plugins.Web, 1.33.0-alpha\"\n",
    "#r \"nuget: Microsoft.SemanticKernel.Yaml, 1.33.0\"\n",
    "#r \"nuget: Microsoft.SemanticKernel.Agents.Core, 1.33.0-alpha\"\n",
    "#r \"nuget: Microsoft.SemanticKernel.Connectors.AzureAISearch, 1.33.0-preview\"\n",
    "#r \"nuget: Microsoft.SemanticKernel.Connectors.AzureOpenAI, 1.33.0\"\n",
    "#r \"nuget: Microsoft.SemanticKernel.Connectors.InMemory, 1.33.0-preview\"\n",
    "\n",
    "// Import the necessary libraries\n",
    "using System;\n",
    "using System.Threading;\n",
    "using System.Net;\n",
    "using System.ComponentModel;\n",
    "using System.Diagnostics;\n",
    "using System.IO;\n",
    "using System.Text.Json;\n",
    "using System.Threading.Tasks;\n",
    "using System.Globalization;\n",
    "using Azure.Identity;\n",
    "using CsvHelper;\n",
    "using Microsoft.DotNet.Interactive;\n",
    "using Microsoft.Extensions.Configuration;\n",
    "using Microsoft.Extensions.DependencyInjection;\n",
    "using Microsoft.Extensions.VectorData;\n",
    "using Microsoft.Extensions.Http.Resilience;\n",
    "using Microsoft.SemanticKernel;\n",
    "using Microsoft.SemanticKernel.Agents;\n",
    "using Microsoft.SemanticKernel.Agents.Chat;\n",
    "using Microsoft.SemanticKernel.Agents.History;\n",
    "using Microsoft.SemanticKernel.ChatCompletion;\n",
    "using Microsoft.SemanticKernel.Connectors.AzureAISearch;\n",
    "using Microsoft.SemanticKernel.Connectors.AzureOpenAI;\n",
    "using Microsoft.SemanticKernel.Connectors.InMemory;\n",
    "using Microsoft.SemanticKernel.Data;\n",
    "using Microsoft.SemanticKernel.Embeddings;\n",
    "using Microsoft.SemanticKernel.Plugins.Web.Bing;"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Create Kernel Builder\n",
    "Create a Kernel builder instance."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "// The Agent Framework is experimental and requires warning suppression\n",
    "#pragma warning disable CA2007, IDE1006, SKEXP0050, SKEXP0001, SKEXP0110, SKEXP0010, OPENAI001\n",
    "\n",
    "// NOTE: Azure AI Search or an in memory vector store can be used.\n",
    "var useAzureAISearch = false;\n",
    "var collectionName = \"products\";\n",
    "\n",
    "// Read config settings\n",
    "var configBuilder = new ConfigurationBuilder()\n",
    "    .SetBasePath(Directory.GetCurrentDirectory())\n",
    "    .AddJsonFile(\"./appsettings.json\", optional: false)\n",
    "    .AddJsonFile(\"./appsettings.Development.json\", optional: false);   \n",
    "var configuration = configBuilder.Build();\n",
    "\n",
    "IKernelBuilder builder = Microsoft.SemanticKernel.Kernel\n",
    "    .CreateBuilder()\n",
    "    .AddAzureOpenAITextEmbeddingGeneration(\n",
    "        configuration[\"EmbeddingModelDeployment\"],\n",
    "        configuration[\"Endpoint\"],\n",
    "        new AzureDeveloperCliCredential())\n",
    "    .AddAzureOpenAIChatCompletion(\n",
    "        configuration[\"ChatModelDeployment\"],\n",
    "        configuration[\"Endpoint\"],\n",
    "        new AzureDeveloperCliCredential());\n",
    "\n",
    "if (useAzureAISearch)\n",
    "{\n",
    "    builder = builder.AddAzureAISearchVectorStore(\n",
    "                new Uri(configuration[\"AzureAISearchEndpoint\"]),\n",
    "                new AzureDeveloperCliCredential());\n",
    "}\n",
    "else\n",
    "{\n",
    "    builder = builder.AddInMemoryVectorStore();\n",
    "}\n",
    "\n",
    "builder.Services.ConfigureHttpClientDefaults(c =>\n",
    "{\n",
    "    c.AddStandardResilienceHandler().Configure(o =>\n",
    "    {\n",
    "        o.TotalRequestTimeout.Timeout = TimeSpan.FromSeconds(120);\n",
    "        o.AttemptTimeout.Timeout = TimeSpan.FromSeconds(60);\n",
    "        o.CircuitBreaker.SamplingDuration = TimeSpan.FromSeconds(120);\n",
    "    });\n",
    "});\n",
    "\n",
    "private sealed class FunctionInvocationFilter() : IFunctionInvocationFilter\n",
    "{\n",
    "    public async Task OnFunctionInvocationAsync(FunctionInvocationContext context, Func<FunctionInvocationContext, Task> next)\n",
    "    {\n",
    "        if (context.Function.PluginName == \"SearchPlugin\")\n",
    "        {\n",
    "            Console.WriteLine($\"{context.Function.Name}:{JsonSerializer.Serialize(context.Arguments)}\");\n",
    "        }\n",
    "        await next(context);\n",
    "    }\n",
    "}\n",
    "\n",
    "builder.Services.AddSingleton<IFunctionInvocationFilter, FunctionInvocationFilter>();\n",
    "\n",
    "// builder.Services.AddSingleton(Microsoft.Extensions.Logging.LoggerFactory.Create(builder =>\n",
    "// {\n",
    "//     //builder.AddConsole();\n",
    "//     // NOTE: change to Trace if you want to see raw tool responses\n",
    "//     builder.SetMinimumLevel(LogLevel.Trace);\n",
    "// }));\n",
    "\n",
    "Microsoft.SemanticKernel.Kernel kernel = builder.Build();\n",
    "\n",
    "Microsoft.SemanticKernel.Kernel bingKernel = kernel.Clone();\n",
    "var textSearch = new BingTextSearch(apiKey: configuration[\"BingAPIKey\"]);\n",
    "var searchPlugin = textSearch.CreateWithSearch(\"SearchPlugin\");\n",
    "bingKernel.Plugins.Add(searchPlugin);\n",
    "\n",
    "Microsoft.SemanticKernel.Kernel vectorSearchKernel = kernel.Clone();\n",
    "var vectorStore = vectorSearchKernel.Services.GetRequiredService<IVectorStore>();\n",
    "var recordCollection = vectorStore.GetCollection<string, ProductDataModel>(collectionName);\n",
    "await recordCollection.CreateCollectionIfNotExistsAsync();\n",
    "var textEmbeddingGeneration = vectorSearchKernel.Services.GetRequiredService<ITextEmbeddingGenerationService>();\n",
    "var vectorTextSearch = new VectorStoreTextSearch<ProductDataModel>(recordCollection, textEmbeddingGeneration);\n",
    "var vectorSearchPlugin = vectorTextSearch.CreateWithGetTextSearchResults(\"SearchPlugin\");\n",
    "vectorSearchKernel.Plugins.Add(vectorSearchPlugin);\n",
    "\n",
    "private sealed class ProductDataModel\n",
    "{\n",
    "    [VectorStoreRecordKey]\n",
    "    public string Key { get; set; }\n",
    "\n",
    "    [VectorStoreRecordData]\n",
    "    [TextSearchResultName]\n",
    "    public string Name { get; set; }\n",
    "\n",
    "    [VectorStoreRecordData]\n",
    "    [TextSearchResultValue]\n",
    "    public string Content { get; set; }\n",
    "\n",
    "    [VectorStoreRecordVector(1536)]\n",
    "    public ReadOnlyMemory<float> Embedding { get; set; }\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Import sample products into vector store"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "using (var reader = new StreamReader(\"./../data/products.csv\"))\n",
    "using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))\n",
    "{\n",
    "    var records = csv.GetRecords<dynamic>().ToList();\n",
    "\n",
    "    foreach (var record in records)\n",
    "    {\n",
    "        var product = new ProductDataModel\n",
    "        {\n",
    "            Key = Guid.NewGuid().ToString(),\n",
    "            Name = record.Name,\n",
    "            Content = record.Content,\n",
    "            Embedding = await textEmbeddingGeneration.GenerateEmbeddingAsync((string)record.Name + \"\" + (string)record.Content)\n",
    "        };\n",
    "        await recordCollection.UpsertAsync(product);\n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Create Researcher and Marketing agents, execute them"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "// The Agent Framework is experimental and requires warning suppression\n",
    "#pragma warning disable CA2007, IDE1006, SKEXP0001, SKEXP0110, OPENAI001\n",
    "\n",
    "const string ResearcherName = \"Researcher\";\n",
    "const string MarketingName = \"Marketing\";\n",
    "const string WriterName = \"Writer\";\n",
    "const string EditorName = \"Editor\";\n",
    "\n",
    "string researcherYaml = File.ReadAllText(\"./../ChatApp.WebApi/Agents/Prompts/researcher.yaml\");\n",
    "ChatCompletionAgent researcherAgent =\n",
    "    new(KernelFunctionYaml.ToPromptTemplateConfig(researcherYaml))\n",
    "    {\n",
    "        Name = ResearcherName,\n",
    "        Kernel = bingKernel,\n",
    "        Arguments =\n",
    "            new KernelArguments(\n",
    "                new AzureOpenAIPromptExecutionSettings() \n",
    "                { \n",
    "                    FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() \n",
    "                })\n",
    "    };\n",
    "\n",
    "string marketingYaml = File.ReadAllText(\"./../ChatApp.WebApi/Agents/Prompts/marketing.yaml\");\n",
    "ChatCompletionAgent marketingAgent =\n",
    "    new(KernelFunctionYaml.ToPromptTemplateConfig(marketingYaml))\n",
    "    {\n",
    "        Name = MarketingName,\n",
    "        Kernel = vectorSearchKernel,\n",
    "        Arguments =\n",
    "            new KernelArguments(\n",
    "                new AzureOpenAIPromptExecutionSettings() \n",
    "                { \n",
    "                    FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() \n",
    "                })\n",
    "    };\n",
    "\n",
    "var researchContext = \"Can you find the camping trends in 2024 and what folks are doing in this winter?\";\n",
    "var productContext = \"Can you use a selection of tents and sleeping bags as context?\";\n",
    "var assignment = @\"Write a fun and engaging article that includes the research and product information. \n",
    "                    The article should be between 600 and 800 words.\n",
    "                    Make sure to cite sources in the article as you mention the research not at the end.\";\n",
    "\n",
    "StringBuilder sbResearchResults = new();\n",
    "await foreach (ChatMessageContent response in researcherAgent.InvokeAsync(new ChatHistory(), new(){{ \"research_context\", researchContext } }))\n",
    "{\n",
    "    Console.WriteLine();\n",
    "    Console.WriteLine($\"{response.AuthorName.ToUpperInvariant()}:{Environment.NewLine}{response.Content}\");\n",
    "    sbResearchResults.AppendLine(response.Content);\n",
    "}\n",
    "\n",
    "StringBuilder sbProductResults = new();\n",
    "await foreach (ChatMessageContent response in marketingAgent.InvokeAsync(new ChatHistory(), new(){{ \"product_context\", productContext } }))\n",
    "{\n",
    "    Console.WriteLine();\n",
    "    Console.WriteLine($\"{response.AuthorName.ToUpperInvariant()}:{Environment.NewLine}{response.Content}\");\n",
    "    sbProductResults.AppendLine(response.Content);\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Creat Writer and Editor agents, put them into Group Chat"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "// The Agent Framework is experimental and requires warning suppression\n",
    "#pragma warning disable CA2007, IDE1006, SKEXP0001, SKEXP0110, OPENAI001\n",
    "\n",
    "private sealed class NoFeedbackLeftTerminationStrategy : TerminationStrategy\n",
    "{\n",
    "    // Terminate when the final message contains the term \"Article accepted, no further rework necessary.\" - all done\n",
    "    protected override Task<bool> ShouldAgentTerminateAsync(Agent agent, IReadOnlyList<ChatMessageContent> history, CancellationToken cancellationToken)\n",
    "    {\n",
    "        if(agent.Name != EditorName)\n",
    "            return Task.FromResult(false);\n",
    "\n",
    "        return Task.FromResult(history[history.Count - 1].Content?.Contains(\"Article accepted\", StringComparison.OrdinalIgnoreCase) ?? false);\n",
    "    }\n",
    "}\n",
    "\n",
    "\n",
    "\n",
    "string writerYaml = File.ReadAllText(\"./../ChatApp.WebApi/Agents/Prompts/writer.yaml\");\n",
    "ChatCompletionAgent writerAgent =\n",
    "    new(KernelFunctionYaml.ToPromptTemplateConfig(writerYaml))\n",
    "    {\n",
    "        Name = WriterName,\n",
    "        Kernel = kernel,\n",
    "        Arguments = new()\n",
    "    };\n",
    "\n",
    "string editorYaml = File.ReadAllText(\"./../ChatApp.WebApi/Agents/Prompts/editor.yaml\");\n",
    "ChatCompletionAgent editorAgent =\n",
    "    new(KernelFunctionYaml.ToPromptTemplateConfig(editorYaml))\n",
    "    {\n",
    "        Name = EditorName,\n",
    "        Kernel = kernel,\n",
    "    };\n",
    "\n",
    "AgentGroupChat chat =\n",
    "    new(writerAgent, editorAgent)\n",
    "    {\n",
    "        ExecutionSettings = new AgentGroupChatSettings\n",
    "        {\n",
    "            SelectionStrategy = new SequentialSelectionStrategy(){ InitialAgent = writerAgent },\n",
    "            TerminationStrategy = new NoFeedbackLeftTerminationStrategy()\n",
    "        }\n",
    "    };\n",
    "\n",
    "Console.WriteLine(\"Ready!\");"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Execute Group Chat"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "// The Agent Framework is experimental and requires warning suppression\n",
    "#pragma warning disable CA2007, IDE1006, SKEXP0001, SKEXP0110, OPENAI001\n",
    "\n",
    "writerAgent.Arguments[\"research_context\"] = researchContext;\n",
    "writerAgent.Arguments[\"research_results\"] = sbResearchResults.ToString();\n",
    "writerAgent.Arguments[\"product_context\"] = productContext;\n",
    "writerAgent.Arguments[\"product_results\"] = sbProductResults.ToString();\n",
    "writerAgent.Arguments[\"assignment\"] = assignment;\n",
    "\n",
    "try\n",
    "{\n",
    "    await foreach (ChatMessageContent response in chat.InvokeAsync())\n",
    "    {\n",
    "        Console.WriteLine();\n",
    "        Console.WriteLine(\"------------------------\");\n",
    "        Console.WriteLine($\"{response.AuthorName.ToUpperInvariant()}:{Environment.NewLine}{response.Content}\");\n",
    "    }\n",
    "}\n",
    "catch (HttpOperationException exception)\n",
    "{\n",
    "    Console.WriteLine(exception.Message);\n",
    "    if (exception.InnerException != null)\n",
    "    {\n",
    "        Console.WriteLine(exception.InnerException.Message);\n",
    "        if (exception.InnerException.Data.Count > 0)\n",
    "        {\n",
    "            Console.WriteLine(JsonSerializer.Serialize(exception.InnerException.Data, new JsonSerializerOptions() { WriteIndented = true }));\n",
    "        }\n",
    "    }\n",
    "}"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".NET (C#)",
   "language": "C#",
   "name": ".net-csharp"
  },
  "language_info": {
   "name": "python"
  },
  "polyglot_notebook": {
   "kernelInfo": {
    "defaultKernelName": "csharp",
    "items": [
     {
      "aliases": [],
      "name": "csharp"
     }
    ]
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
